ProgrammingIncluded

Blog v1: From Ruby to NodeJS

A Journey from Middleman to Metalsmith and What I Learned

Charles Chen | Sun 2025-02-16 07:53 PM GMT-8

Table of Contents

By the time you read this post, my website should now be built using a new backend, MetalSmith written in Javascript.

Did you know what this website was first written using Ruby using Middleman? I was just starting to learn web design when I created the website. Having now been 5 years in the fast-paced tech industry, my tastes and preferences have changed.

It's been almost 1.5 years since my last post and I hope with the recent additons to the website, I can focus more on the synthesis of blogs. Why not kick start the month with a rewrite of the website's static site generator? I hope to highlight a few fun technological things as well as factors for my I chose the tools that I did.

A Historical Recap

Looking at my commits, this website has come a long way, here is some timeline:

2017: The Website that was Birthed in a College Dorm

At this stage, the website was a glorified about me page with a small blog section. Raw HTML posts with Middleman were my method of posting, writing was a chore as I had to write every new break line. Technically the backend was written with .html.erb files which were server-based HTML files that are compiled. Similar to PHP side-html files.

Why Middleman?

  • Easy generation of blog-like sites by supporting html to pagination.
  • Auto generation of url's without index.html.
  • Plugins allowed for easy image and recommendation optimizations.
  • sass support which isa more-powerful css variant at the time.

I remember these features as being revolutionary at the time but things have changed a lot since then, especially with libraries from sass now that css supports more features. Libraries like bourbon was revolutionary at the time allowing for creation of grid-boxes making dynamic websites for mobile easier. But these tools have largely been uneeded now with modern css.

I barely knew Ruby and had some projects in PHP + HTML/CSS. HTML5 was still a stranger to me and the idea of NodeJS and Javascript was still foreign to me. I knew I wanted a portfolio website but didn't know what to make of it.

Little did I know most of my skills I learn here would help protoype my thesis project.

I had posted a blog update to kick start this site.

2018: New Aesthetic Changes, adding Disqus, and Ruby Helpers.

By this stage, I had added a couple of plugins of Disqus and then a few Ruby helpers. I knew that I wanted to add more features and abilities and so started adding additional helpers so that I can modify HTML constructs as need-be.

Here are a few helpers I would define under config.rb for my Middleman instance:

# For use on header generations so that line headers were more apparent.
def ast(title_name)
  return '<h2 class="ast">' + title_name + '</h2>'
end

# Article subsection title
def asst(title_name)
  return '<h3 class="asst">' + title_name + '</h3>'
end

# Defining helpers for easy of rendering HTML using Ruby `.erb` files.
def pimage(src, params={}, &block)
  defaults = {alt: "", link: "", width: "100%"}
  params = defaults.update(params)

  final_str = image_tag(src, alt: params[:alt], width: params[:width])
  unless params[:link].empty?
    final_str = '<a href="' + params[:link] + '" class="image-link">' + final_str + '</a>'
  end

  unless params[:alt].empty?
    final_str += '<br /><i class="image-subtitle"> image: ' + params[:alt] + '</i>'
  end

  if block_given?
    custom_append = block.call final_str
    return CGI.unescapeHTML('<div class="image-unit">' + custom_append + '</div>')
  end
    return CGI.unescapeHTML('<div class="image-unit">' + final_str + '</div>')
end

Ruby allowed these helpers during the processing phase and added additional flexibility, here is one from the Lunar=Nox post:

<%= pimage "pblog/2021/equinox_thumbnail.png", link: "https://programmingincluded.itch.io/lunarequinox" do
    |img_html|
        + img_html \
        + '<br /> <i class="image-subtitle"> image: Game Jam 2020 Submission! ' \
        + 'You can find the game' \
        + '<a href="https://programmingincluded.itch.io/lunarequinox"> here </a></i>'
    end %>

This change allowed me not only to render the image with custom link, but also to inject a custom subtitle with link support. With Ruby, the ability to create helpers and the wrap it in a compile-side language was powerful and useful in edge-cases.

Ruby showed its strength with its strong out-of-the-box support of plugins such as Disqus but also allowed for powerful helper constructs. I wouldn't recommend doing this now as I think this greatly complicates the render pipeline, however when everything was compile-side *.html.erb it was fairly easy to implement.

2019: Better Listing of Articles

Around 2019, after my work with a Javascript Electron based project for my paper, Improv. My CSS and Javascript skills grew significantly and I took the time to flush out the blog index page tapping more into the Ruby ecosystem.

Index page, the blue used to be red.
image: Index page, the blue used to be red.

The index page we see today with the cards and category render on the top-right hand side was more-or-less the same since then.

2021: Ruby Upgrades and Middleman Upgrades

Around this time, the Ruby version I used was becoming hard to procure and install on Windows. So I updated my Ruby version and upgraded Middleman. This broke a few of my plugins and forced me fork a few plugins. Vital plugins such as the one used to create my related articles section began breaking as some of them were tied to custom Windows build binaries that were no longer supported.

At this point they were minor inconveniences, perhaps standard to software maintenance. The idea of a rewrite began to brew in my mind however.

2023: Dynamic Table of Contents and Signs of Age

A time-skip happened as I started working at Nvidia. The first few years were quite busy so I only focused on writing a couple of articles. But in the process that year, I decided to add a dynamic side-bar after noticing them appear throughout the tech landscape.

Around this time, I also supported markdown rendering on the render pipeline. So my files became .html.md.erb.

Client side table of contents.
image: Client side table of contents.

The sidebar was entirely written with JQuery and otherwise vanilla Javascript. I am sure there are probably libraries out there now but this was written partly as a way for me to learn. I needed a client-side way to render TOC and at the time my backend was in Ruby still so it was not immediately clear to me the way to go.

Painful Build Troubles

It was around this time I ran into lots of build troubles that had accumulated over the years:

  • I created a custom docker image to stabilize my Ruby version.
  • My recommendation plugin started breaking as the plugin was no longer maintained.
    • I had to create a custom fork.
  • Middleman rendering started breaking for some of my exotic files because the checksum check was broken.
    • I believe one of my plugins had miss interpreted a few files if a certain keyword appeared causing checksum errors.
  • A build.py was introduced so that I would work around the problem by skipping broken checksum files and then injecting them to the payload.

I started to consider a rewrite in the world of NodeJs and came across Metalsmith. This rewrite would start in 2023, but I wouldn't come back to it until 2025... as I started becoming busy once more.

2025: Brand New Backend: Metalsmith

In 2023, I had chosen Metalsmith and had started a feat-v2 branch with a basic render pipeline setup. But it wasn't a couple days ago did I start seriously looking into the ecosystem and hooking up the framework. It took about 16 hours of continuous porting between 2 years ago me and now to finally finish the port.

But what led me to choose Metalsmith and why NodeJS? I want to dedicate the rest of the article on this topic as well as highlight a few neat features.

Why Metalsmith?

After almost 8 years of maintaining the website, I've developed a list of things I wanted to support or have learned.

NodeJS Meant Options

For me NodeJS became my go-to web development language. Over the years I had worked with meteor for my thesis project, browserify for the frontend, as well as Angular. With recent experience with vite.js it only solidified my preference in NodeJS. These are just the NodeJs frameworks, I had also learned Laravel pre-2020 days. All these various frameworks made me realize the surprising amount of options available, each with slightly different ways of doing things.

At somepoint, I had toyed with writing my own backend framework with Handlebars and webpack though I had forgotten what I was using it for. Regardless, these skills proved useful as almost every framework can be reduced down to a few components:

  • Backend templating engine for rendering and compiling down to html deliverables.
  • Frontend Javascript package which allows for interactive UI elements.
  • Live Web Debugging which allows for fast development cycles.
  • Good logging for large server projects.

If you have these few components set, it makes things a lot easier to maintain. What NodeJs provided were options along each of these pillars in making a good web language.

To me, NodeJs meant the ability to customize every step of the web pipeline.

Ruby (more specifically Ruby on Rails) felt revolutionary at the time, introduce Gem.lock files and template rendering when compared to alternatives such as PHP at the time. However, much of what ROR offerred could be found in NodeJs ecosystem. ROR in comparison felt much more rigid albeit faster in prototyping.

What About Other Languages?

I had considered Rust as a potential but the ecosystem still feels too young. NodeJs seems to me equally if not more flexible ecosystem compared to ROR.

As a result, I ended up looking into the NodeJs ecosystem for a good static site generator.

Easily Extensible

I needed a framework that wasn't too rigid. There were some framework where it only accepted Markdown and generated a very standard site. However, I wanted something more customizable and something I can dissect in case I ever I need to in house a plugin for easy maintenance.

At its core, Metalsmith is about extensbility and is not very vocal about how things should be structured. Instead it provides concepts of out the box:

  • Virtual Files and Metadata
  • Plugins
  • Queriable Regex for Selecting Files

A pipeline composes of plugins and transformations of a virtual file system represented by a single dictionary.

Each file from a folder is given an object with content and metadata encoded. Each plugin introduced would transform and operate on this list of files:

let files = {
  "path_to_file.html": {metadata_1: "", content: ""},
  "folder/naother.html": {metadata_1: "", content: ""}
}

// Modifying the render pipeline is as simple as modifying the dictionary and path:
files["new_file.html"] = {...}

The pipeline for Metalsmith effectively becomes a chain of functions each mutating the dicionary.

let plugin = (files, metalsmith) => {
  // mutate `files` or read meta-metadata from `metalsmith`.
}

Here is what a basic config file looks like:

// Read markdown files from source recursively and output html files in build folder.
let ms = Metalsmith("working_directory")
  .source('./source')
  .destination('./build')
  .use(inPlace({transform: "markdown-it"}))
  .build()

Any plugin becomes a file signature of (files, metalsmith) which makes it easy to onboard. As of the time of this writing, most features supported out-of-the-box by many static site-generators are instead packaged as plugins. Furthermore, many of these plugins use NodeJS libraries under-the-hood.

Examples like:

  • metalsmith/collections which groups files, sorts them, and indexes them then exposes them as metadata.
  • metalsmith/layouts uses tried and true handlebars.js to render re-usable side-wide templates.

These are plugins maintained by the official devs which makes it easier to onboard too.

Support a Simple and Stable Content Encoding

Since 2017, Markdown has gained significantly popularity both in documentation as well as blog-like frameworks. Having made the switch to Markdown in Middleman at somepoint, I knew at that point that writing in Markdown was the way to go for me, specifically Markdown with custom HTML support. That way I can focus on the content and media generation rather than wrap my head around how the HTML will render.

With Metalsmith this was achievable with a simple plugin: metalsmith-inplace which renders your documents in-place using jstransformer libraries. For my AI friends, jstransformer is not about transformers in neural network architecture but rather it is a library for standardizing well known language compilers in Javascript into a single interface.

import inPlace from '@metalsmith/in-place'

// It is as easy as three lines of code!
Metalsmith("working_dir")
  .use(inPlace({transform: "markdown-it", engineOptions: {html: true}}))
  .build()

Template and Compile-Time Rendering

To make porting easy and also powerful, I knew I needed something that would support templating. As mentioned above, metalsmith/layouts would support what I needed. Most of my code was not compatible with css however and I needed a sass backend, thankfully there was metalsmith/sass package.

This was what sealed the deal for me, the sass support as I needed a way to port easily. The sass work was done around 2023, it took sometime to remove outdated dependencies and move most into pure sass and flex-grid implementations.

Another familiar addition was handlebars.js support. Now my files can be rendered using the extension .html.md.hbs which correlates well with my old workflow.

Happily Ever After?

Metalsmith did have its limitations. When I first looked into Metalsmith, the project was about 2.5 years old, with the first official build in 2020. Coming into 2025, the project is now 5 years old, however it is still well maintained. Some 3rd party plugins, however, saw little action and some of them consolidated to large repos. But even then, the repos had little or few issues. Perhaps it is because the framework wasn't as popular, the plugins not as well used, or just well written?

The core plugins and core project do not have this issue however. Furthermore, most of the plugins I used worked well probably thanks to how the project incentivizes small plugins.

Lots of Plugins Meant Some Didn't Work

That being said, some were hard to wrangle and utilize. Certain plugins I had hoped to rely-upon did not work well with my setup such as metalsmith-collections-related which had trouble in Windows as it requires a C++ build internally probably for the tokenization of articles.

I ended up folding my own pipeline for recommenders using a simple tag-based system for now. That's for another article!

Lots of Plugins Meant Steeper Learning Curve

Another one was the long onboarding. When multiple plugins are involved, order of registration of plugins become important. I had an issue with how my collections were rendering URLs because I had modified my URLs using a custom pipeline render to parse my specialized naming scheme. But I couldn't just wait until after my custom rename plugin because of how my in-place and layout renders dependended on the collection's metadata.

I ended up having to write my own work-around post-collections pipeline to fix the URL after reading the code on how the collections plugin stored its metadata. Now it may seem a bit hard, but to me, because of how transparent and short the plugins are, it did not take me more than an hour to implement a solution.

To get to where I am today, I read through the plugin writing doc in its entirety to fully grasp how Metalsmith worked. After that reading, everything fit like a puzzle piece. I eneded up writing a pipeline to support how the website was generated from Middleman:

  • Related blogs pipeline
  • Custom blogs file path transformation.
  • Register my own partials for Handlebars.js
  • Register my own helpers for Handlebars.js using handlebars-helpers

Where to go from Here?

As it stands, most of the features of my old workflow has been ported over except for Disqus which I decided to deprecate given the lack of usage. I can render just like before using helpers:

## Markdown Works!
{{pimage "custom_image" alt-"custom text"}}

All my workflow now exists in Markdown and added html supported if need-be.

Using handlebars.js I can register almost any function if not already supported by handlebars-helpers which expedite the flow.

I hope this workflow will last me a few years until some dependency chain breaks and I am forced to dockerize yet again. But hopefully by then, Rust will grow mature enough for me to use it as a static website generator. I am already familiar with Zola however found it too rigid for my tastes.

Perhaps Rust will eventually provide plenty of tools to mix and match my webstack just like NodeJs one day? Perhaps WebAssembly will replace Javascript? Perhaps.